package com.adrian.misc.fractal;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.adrian.misc.fractal.color.SimpleColorMap;
import com.adrian.misc.fractal.math.ComplexNumber;


public class MandelbrotCanvas extends JComponent implements FractalCanvas, ColorMapListner {

	/**
	 * 
	 */
	
	int xStart = 0;
	int yStart = 0;
	int xEnd = 0;
	int yEnd = 0;
	int lastX = 0;
	int lastY = 0;
	
	private float TOP =0.05f;
	private float BOTTOM =0.7f;
	
	Rectangle selection = new Rectangle();
	private static final long serialVersionUID = 1L;
	Fractal fractal;
	//Fractal fractal;
	FractalParameters fractParam;
	FractalListner fractListner;
	FractalInterface fInterface;
	Thread fractalPainter; 
	boolean isSelectionMove = false;
	boolean inSync = false;
	boolean colorSync = false;
	boolean isRendering = false;
	Image backbuffer;
	Image refreshbuffer;
	Graphics backg;
	public static final int MODE_MAINTAIN_ASPECT = 1;
	public static final int MODE_STRETCH = 2;
	public static final int MODE_EXPAND_VIEW = 3;
	private int drawMode = 1; 
	double val = 0;
	
	public MandelbrotCanvas (int width, int height) {
		this (null,width,height);
	}
	
	public MandelbrotCanvas (Fractal copyFractal, int width, int height, boolean isSelectable) {
		super();
		setBackground(Color.BLACK);
		setForeground(Color.black);
		setFocusable(true);
		setPreferredSize(new Dimension(width,height));
		this.setOpaque(true);
		this.setBackground(Color.WHITE);
		fInterface = new FractalInterface(this);
		backbuffer = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
		refreshbuffer = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
		backg = backbuffer.getGraphics();
		if ( copyFractal!= null ){
			FractalParameters fp = new FractalParameters(copyFractal.getFractalParam());
			int size = width<height?width:height;
			int szDiff = (width - height);
			fp.fAreaMap.height = size;
			fp.fAreaMap.width = size;
			fp.fAreaMap.xStart = szDiff > 0? szDiff/2:0;
			fp.fAreaMap.yStart = szDiff < 0? szDiff/2:0;
			System.out.println("Before Compy- parameters--->" +fp);
			fractal  = new Mandelbrot(this,fp);
			System.out.println("Fractal created** = " + fractal.getFractalParam());
		} else {
			fractal  = new Mandelbrot(this,new SimpleColorMap("Ultra simple",Color.BLACK, Color.RED, Color.YELLOW,255),100);
		}
		
		fractalPainter = new Thread ((Runnable)fractal);
		if (fractListner!=null) fractListner.FractalChanged(fractal);
		fractalPainter.start();
		fractParam = fractal.getFractalParam();
		inSync = true;
		if ( isSelectable) {
			addMouseListener(fInterface);
			addMouseMotionListener(fInterface);
		}
		fractal.addChangeListener(fInterface);
		System.out.println("Fractal created = " + fractal.getFractalParam());
	}
	public void updateRefreshBuffer() {
		((BufferedImage)backbuffer).copyData(((BufferedImage)refreshbuffer).getRaster());
	}
	
	public MandelbrotCanvas (Fractal copyFractal, int width, int height) {
		this(copyFractal,width,height,true);
	}
	
	public void registerFractalListner(FractalListner fl) {
		fractListner = fl;
	}
	
	public void setDrawMode (int mode) {
		drawMode = mode;
		switch (mode){
			case MODE_MAINTAIN_ASPECT:
				fractal.reset();
				System.out.println("##Area -" + fractal.getFractalParam().fAreaMap);
				break;
			case MODE_STRETCH:
				fractal.reset();
				fractal.getFractalParam().fAreaMap.xStart = 0;
				fractal.getFractalParam().fAreaMap.yStart = 0;
				fractal.getFractalParam().fAreaMap.height = getPreferredSize().height;
				fractal.getFractalParam().fAreaMap.width = getPreferredSize().width;
				System.out.println("##Area -" + fractal.getFractalParam().fAreaMap);
				break;
			case MODE_EXPAND_VIEW:
				int szDiff = getPreferredSize().width - getPreferredSize().height;
				double xInc = szDiff > 0? (szDiff/ (2.0*(double)getPreferredSize().height)):0.0;
				double yInc = szDiff < 0? (szDiff/ (2.0*(double)getPreferredSize().height)):0.0;
				fractal.reset();
				fractal.getFractalParam().fAreaMap.mathMap.cStart.add(
						new ComplexNumber(-xInc*fractal.getFractalParam().fAreaMap.mathMap.width,
								-yInc*fractal.getFractalParam().fAreaMap.mathMap.height));
				fractal.getFractalParam().fAreaMap.mathMap.width = fractal.getFractalParam().fAreaMap.mathMap.width  * (1 + 2.0*xInc); 
				fractal.getFractalParam().fAreaMap.mathMap.height= fractal.getFractalParam().fAreaMap.mathMap.height  * (1 + 2.0*yInc);
				fractal.getFractalParam().fAreaMap.width =  getPreferredSize().width;
				fractal.getFractalParam().fAreaMap.height =  getPreferredSize().height;
				fractal.getFractalParam().fAreaMap.xStart =  0;
				fractal.getFractalParam().fAreaMap.yStart =  0;
				System.out.println("##Area -" + fractal.getFractalParam().fAreaMap);
				break;

		}
		inSync = false;
	//	colorSync = false;
		repaint();
			
	}
	
	@Override
	public void paint(Graphics g) { 
		//System.out.println("Called Paint");
		update(g);
	} 

	public void update(Graphics g) {
//System.out.println("Called Update");
		if (!isRendering) {
			if (!inSync) {
				System.out.println("Renderd mandelbrot-" + fractal.getFractalParam().fAreaMap);
				fractalPainter = new Thread((Runnable)fractal);
				fractalPainter.start();
				fInterface.clear();
				inSync= true;
			}
			if (!colorSync) {
				System.out.println("ColorSyncing mandelbrot" );
				fractal.draw(backg);
				colorSync = true;
			}
		}
		g.clearRect(0, 0, this.getWidth(), this.getHeight());
		g.drawImage(refreshbuffer, 0, 0, this);
//		System.out.println("refresbuffer width=" + refreshbuffer.getWidth(this) + "height=" + refreshbuffer.getHeight(this));
//		System.out.println("Canvas width =" + this.getWidth() + "height=" + this.getHeight());
		if (fractListner!=null)  fractListner.FractalChanged(fractal);
		fInterface.reDraw(g);
	}
	
	public void setSync(boolean val) {
		inSync = val;
	}
	public void setColorSync(boolean val) {
		colorSync = val;
	}	
	public boolean inSync() {
		return inSync;
	}
	public boolean colorSync() {
		return colorSync;
	}
	public Fractal getFractal() {
		return fractal;
	}



	public void colorControlHasChanged(ChangeEvent ce) {
		fractal.getFractalParam().colMap = ((ColorControl)ce.getSource()).getSelectedColorMap();		
	}
	public Rectangle getSelection(){
		return selection;
	}
	
	public void setFromSelection() {
		AreaMapping currParam = fractParam.fAreaMap;
		if (fractalPainter!=null) {
			if (fractalPainter.isAlive()) {
				fractalPainter.stop();
				setRendering(false);
			}
		}
		if ( (xStart==xEnd) && (yStart==yEnd) ) {
			repaint();
			return;
		}
		/*		int width = Math.abs(xEnd - xStart) > Math.abs(yEnd - yStart) ? Math
				.abs(xEnd - xStart) : Math.abs(yEnd - yStart);*/
		int width = selection.width;
		
		double newZX = (currParam.mathMap.width/currParam.width)* selection.getMinX();
		double newZY = (currParam.mathMap.height/currParam.height)* selection.getMinY();
		currParam.mathMap.set( new ComplexNumber(currParam.mathMap.cStart.X + newZX, currParam.mathMap.cStart.Y - newZY) , 
								((double) selection.width /(double)currParam.width) * currParam.mathMap.width,
								((double) selection.width /(double)currParam.height)* currParam.mathMap.height );
		System.out.println("New Zx,Zy,width -" +  newZX +"," +newZY +"," +width );
		System.out.println("Current param -" + fractal.getFractalParam().fAreaMap);
		repaint();
	}
	
	class FractalInterface implements  MouseListener, MouseMotionListener, ChangeListener {

		Color pen = Color.white;
		Color darkfill = new Color(0.0f,0.0f,0.0f,.3f);
		Color lightfill = new Color(1.0f,1.0f,1.0f,.5f);
		Font defaultFont = new Font("Verdana",Font.PLAIN,10);
	//	FractalCanvas com = null;

		
		public FractalInterface(FractalCanvas f) {
			//com = f;
/*			fractal = f.getFractal();
			fractParam = fractal.getFractalParam();*/
		}

		public void mouseClicked(MouseEvent e) {
		}

		public void mouseEntered(MouseEvent e) {
		}

		public void mouseExited(MouseEvent e) {
		}

		public void mousePressed(MouseEvent e) {
			lastX=e.getX();
			lastY=e.getY();
			if (!selection.contains(e.getX(), e.getY()))
			{
				xStart = e.getX();
				yStart = e.getY();
				xEnd = xStart;
				yEnd = yStart;
				isSelectionMove = false;
			} else {
				isSelectionMove = true;
			}
			
			requestFocus();
			System.out.println("Mouse pressed");
		}

		public void mouseReleased(MouseEvent e) {
		}

		public void mouseDragged(MouseEvent e) {
			xEnd = e.getX();
			yEnd = e.getY();
			
			if (!isSelectionMove) {
			int width = Math.abs(xEnd - xStart) > Math.abs(yEnd - yStart) ? Math
					.abs(xEnd - xStart) : Math.abs(yEnd - yStart);
			selection.setBounds(xStart < xEnd ? xStart : (xStart-width),
					yStart < yEnd ? yStart :(yStart-width), width, width);
			} else {
				selection.setLocation((int)selection.x+ (e.getX()-lastX), (int)selection.y + (e.getY()-lastY) );
			}
			lastX=e.getX();
			lastY=e.getY();
			if (fractListner!=null) { fractListner.selectionChanged(MandelbrotCanvas.this);}
			repaint();
		}

		public void mouseMoved(MouseEvent e) {
			val= ((Mandelbrot)fractal).mathPlot[e.getX()][e.getY()];
			repaint();
		}

		public void reDraw(Graphics g) {
			Color origCol = g.getColor();
			g.setColor(pen);
//			System.out.println("Square");
			float LOCATION = TOP;
			/*	int width = Math.abs(xEnd - xStart) > Math.abs(yEnd - yStart) ? Math
					.abs(xEnd - xStart) : Math.abs(yEnd - yStart);
			g.drawRect(xStart < xEnd ? xStart : (xStart-width),
					yStart < yEnd ? yStart :(yStart-width), width, width); */
			g.drawRect(selection.x,selection.y,selection.width,selection.height);
			g.setColor(darkfill);
			g.fillRect(selection.x+1,selection.y+1,selection.width-1,selection.height-1);
			if (fractal.getState() < 1.0 ) {
				//System.out.println("-->" +(float)getPreferredSize().height*(LOCATION+(LOCATION==BOTTOM?.2f:0.0f)));
				g.drawRect((int)((float)getPreferredSize().width *LOCATION)+1, (int)((float)getPreferredSize().height*(LOCATION+(LOCATION==BOTTOM?.2f:0.0f))) +1, 100, 10);
				g.setColor(lightfill);
				g.drawRect((int)((float)getPreferredSize().width *LOCATION), (int)((float)getPreferredSize().height*(LOCATION+(LOCATION==BOTTOM?.2f:0.0f))), 100, 10);
				g.fillRect((int)((float)getPreferredSize().width *LOCATION), (int)((float)getPreferredSize().height*(LOCATION+(LOCATION==BOTTOM?.2f:0.0f))), (int)(100 * fractal.getState()), 10);
				g.setColor(pen);
				g.setFont(defaultFont);
				g.setColor(darkfill);
				g.drawString(""+(int)(fractal.getState()*100)+"%",(int)(getPreferredSize().width *LOCATION + 40)+1, (int)(getPreferredSize().height*(LOCATION+(LOCATION==BOTTOM?.2f:0.0f)))+10 + 1);
				g.setColor(pen);
				g.drawString(""+(int)(fractal.getState()*100)+"%",(int)(getPreferredSize().width *LOCATION + 40), (int)(getPreferredSize().height*(LOCATION+(LOCATION==BOTTOM?.2f:0.0f)))+10);
			}
			g.setColor(lightfill);
			g.drawString("Val="+val,10,10);
			g.setColor(origCol);
		}


		public void clear(){
			xStart = 0;
			yStart = 0;
			xEnd = 0;
			yEnd = 0;
			selection = new Rectangle();
		}
		


		public void stateChanged(ChangeEvent ce) {
			repaint();
		}
	}


	public Graphics getBufferedGraphics() {
		return backg;
	}

	public BufferedImage getImage() {
		return (BufferedImage)backbuffer;
	}

	public void setRendering(boolean val) {
		isRendering = val;
	}

	public boolean isRendering() {
		return isRendering;
	}

	public void setFractal(Fractal fract) {
		FractalParameters fp = new FractalParameters(fract.getFractalParam());
		int width = getPreferredSize().width;
		int height = getPreferredSize().height;
		int szDiff = (width - height);
		int size = width<height?width:height;
		fp.fAreaMap.height = size;
		fp.fAreaMap.width = size;
		fp.fAreaMap.xStart = szDiff > 0? szDiff/2:0;
		fp.fAreaMap.yStart = szDiff < 0? szDiff/2:0;
		System.out.println("Before Compy- parameters--->" +fp);
		fractal  = new Mandelbrot(this,fp);
		System.out.println("Fractal created** = " + fractal.getFractalParam());
	}

	public void reset() {
		fractal.reset();
		fractParam = fractal.getFractalParam();
	}

}
